/**
 * @author admin
 */
package 
{
	import flash.display.Sprite;

	import functions.FunctionNode;
	import functions.AddFunctionNode;
	import functions.ListOfNodes;

	import flash.utils.getDefinitionByName;
	import flash.display.BitmapData;
	import flash.display.Bitmap;
	import flash.events.Event;
	import flash.net.URLLoader;	
	import flash.net.URLRequest;	public dynamic class Evaluator extends Sprite 
	{

		public static var symboltable : Object = new Object();
		symboltable["toString"] = function() 		{ 			return "ROOT-LEVEL SCOPE"; 		};
		public static var objecttable : Object = new Object();
		private static var instance : Evaluator = null;
		public var parentscope : Object;
		public var myxml : XML;
		public var enterframeXML : XML;
		public var bmd : BitmapData;
		public var scopename : String = "uberparentscope";

		public function Evaluator( x : XML = null) 
		{
			instance = this;
			makeFunctionNodes();
			
			
			
			bmd = new BitmapData(500, 400, false, 0x00ff00);
			
			var bm : Bitmap = new Bitmap(bmd);
			
			this.addChild(bm);
			
			for( var i : int = 0;i < 200; i++)
			{
				bmd.setPixel(i, i, 0x0000ff);
			}
			
			if( x == null)
			{			 
				var uLoader : URLLoader = new URLLoader();
				uLoader.load(new URLRequest("flisp3.xml"));

				uLoader.addEventListener(Event.COMPLETE, onXMLLoaded);
			}
			else
			{
				parseXML( x );
			}
		}

		public function onXMLLoaded( e : Event )
		{
			var urlLoader : URLLoader = e.target as URLLoader;
			parseXML( new XML(urlLoader.data) );
		}
		
		public function parseXML( x : XML )
		{
			myxml = x;
			enterframeXML = myxml.descendants("enterframeXML")[0].children()[0];
			trace("enterframeXML=" + enterframeXML);
			var initXML : XML = myxml.descendants("initXML")[0].children()[0];
			trace("initXML = " + initXML);
			Evaluator.evaluate(initXML, this);
			this.addEventListener(Event.ENTER_FRAME, reevaluate);
		}	

		public function reevaluate( e : Event )
		{
			Evaluator.evaluate(enterframeXML, this);
		}

		public function makeFunctionNodes() : void
		{
			var listOfNodes : ListOfNodes = new ListOfNodes();
			var dummyvarAddFunctionNode : AddFunctionNode = new AddFunctionNode();
			
			for( var i : int = 0;i < ListOfNodes.LIST.length; i++)
			{
				var classRef : Class = getDefinitionByName("functions." + ListOfNodes.LIST[ i ]) as Class;
				var myInstance : * = new classRef();

				trace("funcname= " + myInstance.funcname);
				var obname : String = gensym();
				objecttable[ obname ] = myInstance;

				symboltable["symbolvalue" + myInstance.funcname] = <objectliteral>{obname}</objectliteral>;
			}
			this["parentscope"] = symboltable;
		}

		public static function gensym() : String
		{
			
			var str : String = "";
			const numdigits : int = Math.floor(Math.random() * 10);
			for( var i : Number = 0;i < 5 + numdigits; i++)
			{
				str += String.fromCharCode(93 + Math.floor(Math.random() * 26));	
			}
			
			while( objecttable[ str ] != undefined )
			{
				str += String.fromCharCode(64 + Math.floor(Math.random() * 64));
			}
			
			return str;
		}

		public static function evaluate( x : XML, scope : Object ) : XML
		{
			var returnXML : XML;
			
			// well, we need to do different things for different types, right?

			if( String(x.name()).indexOf("literal") > -1)
			{
				return x;
			}
			else
			if ( x.name() == "list" )
			{
				// if it's an empty list, it's NIL -- evaluates to <list/>!
				if ( x.children().length() == 0) return x;
				var funcXML : XML = evaluate(x.children()[ 0 ], scope);
			

				var functionNode : FunctionNode = getLiteral(evaluate(funcXML, scope));
				
				var arglist : XML = <list />;
				
				if( functionNode.functiontype == FunctionNode.FUNCTION ) // for normal functions,
				{ 					// we evaluate each arg before we pass it to the function

					for( var i : int = 1;i < x.children().length(); i++)
					{
						
						var evaled : XML = evaluate(x.children()[ i ], scope);
						
						
						var lengthofargs : int = arglist.children().length();
						
						var lastarg : XML = arglist.children()[ Math.max(lengthofargs - 1, 0) ];
						
						arglist.insertChildAfter(lastarg, evaled);
						//arglist.appendChild( evaled );
					}
		
					
					returnXML = functionNode.getValue(arglist, scope);
				}
				else if( functionNode.functiontype == FunctionNode.MACRO ) // for macros,
				{ 					// we pass raw XML
					for( var i : int = 1;i < x.children().length(); i++)
					{
						arglist.appendChild(x.children()[ i ].copy());
					}
					// remember, a macro just generates *new* XML from the args
					// and expects that we'll evaluate it....
					returnXML = evaluate(functionNode.getValue(arglist, scope), scope);
				}
				else if( functionNode.functiontype == FunctionNode.SPECIALFUNCTION ) // for macros,
				{ 					// we pass raw XML
					for( var i : int = 1;i < x.children().length(); i++)
					{
						arglist.appendChild(x.children()[ i ].copy());
					}
					// a special function is neither fish nor fowl.
					// the args don't get evaluated before being sent,
					// and the return value doesn't get evaluated after it comes back.
					returnXML = functionNode.getValue(arglist, scope);
				}
				
				return returnXML;
			}
			else if( x.name() == "symbol" )
			{
				if( x.hasSimpleContent() )
				{
					return getInScope("symbolvalue" + x.text(), scope);	
				}
				else
				{
					return getInScope("symbolvalue" + evaluate(x.children()[0], scope), scope);
				}
			}
			else if( x.name() == "q" )
			{
				if( x.hasSimpleContent())
				{
					returnXML = <stringliteral>{x.text()}</stringliteral>;	
				}
				else
				{
					returnXML = x.children()[0];
				}
			}
						
			return returnXML;
		}

		public static function getInScope( varname : String, scope : Object ) : XML
		{
			if( scope[ varname ] != undefined )
			{
				return scope[ varname];
			}
			else if ( scope[ "parentscope"] != undefined )
			{
				return getInScope(varname, scope["parentscope"]);
			}
			else
			{
				return null;
			}	
		}

		public static function setInScope( varname : String, value : *, scope : Object ) : void
		{
			scope[ varname ] = value ;
		}

		public static function changeInScope( varname : String, value : *, scope : Object ) : void
		{
			if( scope[ varname ] != undefined )
			{
				scope[ varname ] = value ;
			}
			else
			{
				changeInScope(varname, value, scope[ "parentscope" ]);
			}
		}

		public static function getLiteral( x : XML ) : *
		{

			if( x.name() == "objectliteral" )
			{
				return objecttable[ x.text() ];	
			}	
			else if( x.name() == "numberliteral" )
			{
				return Number(x.text());	
			}
			else if( x.name() == "stringliteral" )
			{
				return x.text();	
			}
			else
			{
				return x.toXMLString();
			}
		}		

		public static function getInstance() : Evaluator
		{
			if ( instance == null )
			{
				instance = new Evaluator();
			}
			return instance;
		}		

		public static function isTrue(t_or_f : XML) : Boolean
		{
			return (t_or_f == <t/>);
		}
	}
}